<?php

namespace org\phpstruct\db\ORM;

use org\phpstruct\core;
use org\phpstruct\db;
use org\phpstruct\db\I;
use org\phpstruct\db\PDO;

class Util {
    const field_PREFIX = 'FLD_';
    const MAPPING_TABLE = 'ENTITY_TABLE';

    public static $typemap = array(
        'resource' => \PDO::PARAM_LOB,
        'integer' => \PDO::PARAM_INT,
        'string' => \PDO::PARAM_STR,
        'NULL' => \PDO::PARAM_NULL,
        'boolean' => \PDO::PARAM_BOOL
    );
    private $db = null;

    public function __construct($db) {
        $this->db = $db;
    }

    public function entityGenerator($tablename, $classname, $package) {

        $db = $this->db;
        switch ($db->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
            default:
            case 'mysql':
                $q = sprintf('DESCRIBE %s', $tablename);
                break;
        }

        $full_classname = $classname;

        $const = sprintf("\t\tconst\tENTITY_TABLE = '%s';\n\n", $tablename);
        $fields = '';
        $getters = '';
        $setters = '';
        $constructor = <<<EOF

		/**
		 * @return {$full_classname}
		 */
		public function __construct(\$data=null) {
			if(is_array(\$data)) {
				\$_r = new \ReflectionClass(__CLASS__);
				
				foreach (\$data as \$k => \$v) {
					
					if(\$_r->getMethod("set_{\$k}")) {
						
						\$method = new \ReflectionMethod(__CLASS__,"set_{\$k}");
						
						\$method->invoke(\$this,\$v);
						
					}
					
				}
			} elseif (is_numeric(\$data)) {
				return Rebel_DB_ORM_Util::findEntityById(__CLASS__,\$data);
			}
		}//:-
		
		public function __get(\$var){
			\$prop = (\$var);
			return isset(\$this->{\$prop}) ? \$this->{\$prop} : null;
		}//
EOF;

        foreach ($db->query($q) as $row) {

            switch ($db->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                case 'mysql':
                    $field_constant = strtoupper($row['FIELD']);
                    $field_name = strtoupper($row['FIELD']);
                    break;
            }
            $const .= "\t\tconst\t\tFLD_" . $field_constant . " = '" . $field_name . "';\r\n";

            $fields .= "\t\tpublic\t \${$field_name};\r\n";

            $getters.= <<<EOF

                /**
                 * @return db\ORM\Field
                 **/
		public function get_{$field_name} (\$asObject=false) {

                    if(\$asObject){
                        return \$this->{$field_name};
                    } else {
                        return \$this->{$field_name}->getValue();
                    }
		}//:-
EOF;

            $setters.= <<<EOF

		public function set_{$field_name} (db\ORM\Field \${$field_name}) {
			\$f = db\\ORM\\FieldFactory::factory(\$field_name, \${$field_name});
			\$this->{$field_name} = \$f;
		
		}//:-
EOF;
        }


        $parts = \explode('.', $_SERVER['HTTP_HOST']);
        $tld = array_pop($parts);
        $name = array_pop($parts);

        $class = <<<EOF
<?php

namespace {$package};

use org\\phpstruct\\core;
use org\\phpstruct\\core\\dto;
use org\\phpstruct\\core\\i;
use org\\phpstruct\\db;

class {$full_classname} extends db\\ORM\\Entity {


	{$const}
	
	{$fields}
	
	{$constructor}
	
	{$getters}
	
	{$setters}

}///:-
?>
EOF;

        $path = addslashes(sprintf('%s%s%s%s%s%s%s%s%s',
                                $_SERVER['DOCUMENT_ROOT'],
                                DIRECTORY_SEPARATOR,
                                '.phpstruct',
                                DIRECTORY_SEPARATOR,
                                'lib',
                                DIRECTORY_SEPARATOR,
                                \str_replace('\\', \DIRECTORY_SEPARATOR, $package),
                                DIRECTORY_SEPARATOR,
                                $classname . '.php'));

        echo "creating $path";
        echo "<xmp>";
        print_r($class);
        echo "</xmp>";

        umask(0000);

        if ($fp = fopen($path, 'w+')) {
            fwrite($fp, $class, 16000);
            return true;
        }

        echo "<pre>";
        echo $class;
        echo "</pre>";
    }

    public function dbImport($package='orm\entities') {
        try {

            $db = $this->db;

            switch ($db->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                case 'mysql':
                    $q = 'SHOW TABLES';
                    $res = $db->query($q);
                    while ($table = $res->fetchColumn()) {
                        $t1 = explode('_', $table);
                        $parts = \array_map(create_function('$t', 'return ucfirst($t);'), $t1);
                        $class = \implode('', $parts);
                        self::entityGenerator($table, $class, $package);
                    }
                    break;
            }
        } catch (\Exception $e) {
            \xdebug_var_dump($e);
        }
    }

    private function getTableMetadataAsObject($table) {

        try {

            $db = $this->db;

            switch (($db->getAttribute(PDO::ATTR_DRIVER_NAME))) {
                case 'OCI':
                    $q = "select column_name, DATA_TYPE from user_tab_columns where TABLE_NAME = :tablename order by COLUMN_ID";
                    $stmt = $db->prepare($q);

                    $stmt->bindValue(':tablename', $table);

                    $stmt->execute();

                    $fields = array();
                    while ($field = $stmt->fetchObject()) {
                        $fields[$field->column_name] = $field;
                    }
                    break;
                case 'MYSQL':
                    $q = "DESCRIBE $table";

                    $stmt = $db->query($q);

                    $fields = array();
                    while ($field = $stmt->fetchObject()) {
                        $fields[$field->field] = $field;
                    }
                    break;
            }

            return $fields;
        } catch (\Exception $e) {
            throw $e;
        }
    }

//

    public function getSqlfieldList($classname, $data) {
        $data = self::dtoToArray($data);
        $r = new ReflectionClass($classname);

        $c = $r->getConstants();

        $keys = $this->getEntityKeys($classname, true);

        $fields = '';
        $sep = '';
        foreach ($c as $k => $v) {
            if (in_array($v, $keys))
                continue;
            if (isset($data[($v)])) {
                if (preg_match("/^" . self::field_PREFIX . "/", $k)) {
                    $fields.= sprintf('%s%s', $sep, $v);
                    $sep = ',';
                }
            }
        }
        foreach ($keys as $pk) {

            $fields.= sprintf('%s%s', $sep, $pk);
            $sep = ',';
        }
        return $fields;
    }

//:-

    public function getSqlInsertPreparedValues($classname, $data) {
        $data = self::dtoToArray($data);
        $r = new ReflectionClass($classname);

        $c = $r->getConstants();

        $keys = $this->getEntityKeys($classname, true);

        $metafields = $this->getTableMetadataAsObject($c['ENTITY_TABLE']);

        $fields = '';
        $sep = '';
        foreach ($c as $k => $v) {
            if (in_array($v, $keys))
                continue;
            if (isset($data[($v)])) {
                if (preg_match("/^" . self::field_PREFIX . "/", $k)) {

                    if (is_object($metafields[$v])) {
                        switch (($metafields[$v]->TYPE)) {
                            case 'DATE':
                                if (strpos($data[$v], ":") === false)
                                    $fields.= sprintf("%sto_date(:%s,'YYYY-MM-DD') ", $sep, $v);
                                else
                                    $fields.= sprintf("%sto_date(:%s,'YYYY-MM-DD HH24:MI:SS')", $sep, $v);
                                break;
                            default:
                                $fields.= sprintf('%s:%s ', $sep, $v);
                                break;
                        }
                        $sep = ',';
                    }
                }
            }
        }

        foreach ($keys as $pk) {

            $fields.= sprintf('%s:%s', $sep, $pk);
            $sep = ',';
        }

        return $fields;
    }

//:-

    public function getSqlUpdatePreparedValues($classname, $data) {
        $data = self::dtoToArray($data);
        $r = new ReflectionClass($classname);

        $c = $r->getConstants();

        $keys = $this->getEntityKeys($classname);

        $metafields = $this->getTableMetadataAsObject($c['ENTITY_TABLE']);

        $fields = '';
        $sep = '';
        foreach ($c as $k => $v) {
            if (in_array($v, $keys))
                continue;
            if (isset($data[$v])) {
                if (preg_match("/^" . self::field_PREFIX . "/", $k)) {

                    if (is_object($metafields[$v])) {
                        switch (($metafields[$v]->DATA_TYPE)) {
                            case 'DATE':
                                $fields.= sprintf("%s %s = to_date(:%s,'yyyy-mm-dd') ", $sep, $v, $v);
                                break;
                            default:
                                $fields.= sprintf('%s %s = :%s ', $sep, $v, $v);
                                break;
                        }
                        $sep = ',';
                    }
                }
            }
        }
        return $fields;
    }

//:-

    public function getWhereClause($classname, $data, $requirePK=true, $onlyPK=false, $globbing=true) {
        $data = self::dtoToArray($data);
        $r = new ReflectionClass($classname);
        $table = $r->getConstants();

        $str = 'where ';
        $and = '';

        if ($requirePK) {
            $ks = $this->getEntityKeys($classname);
            foreach ($ks as $pk) {
                if (!isset($data[$pk])) {
                    throw new ORMException(ORMException::MSG_PRIMARY_KEY_REQUIRED);
                }
            }
        }

        $count = 0;
        foreach ($data as $k => $v) {
            if ($globbing) {
                $count++;
            }
            //If true, build a condition only based on primary keys
            if ($onlyPK) {
                if (!in_array($k, $ks)) {
                    continue;
                }
            }
            $str.= sprintf(' %s trim(%s) = :%s', $and, $k, $k);
            $and = 'and';
        }
        if ($globbing) {
            return $count ? $str : null;
        }
        return $str;
    }

//:-

    public function getEntityKeys($classname, $withUnique=false) {
        $keys = array();
        $db = $this->db;
        $r = new ReflectionClass($classname);

        if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
            $q = sprintf('describe %s', $r->getConstant(self::MAPPING_TABLE));
            $ks = $db->query($q);

            foreach ($ks as $row) {
                if ($row['KEY'] == 'PRI') {
                    array_push($keys, ($row['field']));
                }
            }
            return $keys;
        } else {
            $strUniqueCond = "";
            if ($withUnique)
                $strUniqueCond = " or cons.constraint_type = 'U'";

            $q = sprintf(
                            "SELECT cols.table_name, cols.column_name, cols.position, cons.status, cons.owner
	FROM all_constraints cons, all_cons_columns cols
	WHERE cols.table_name = %s
	AND (cons.constraint_type = 'P' %s )
	AND cons.constraint_name = cols.constraint_name
	AND cons.owner = cols.owner
	AND (cons.owner = 'NUMAS' OR cons.owner = 'DEV_NUMAS')
	ORDER BY cols.table_name, cols.position", ($db->quote($r->getConstant(self::MAPPING_TABLE))), $strUniqueCond);

            $ks = $db->query($q);

            foreach ($ks as $row) {
                array_push($keys, ($row['column_name']));
            }
            return $keys;
        }
    }

//:-

    public function deleteEntity($classname, $data) {
        $data = self::dtoToArray($data);
        $db = $this->db;
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();
        $q = sprintf('delete from %s %s',
                        $r->getConstant(self::MAPPING_TABLE),
                        $this->getWhereClause($classname, $data));

        $stmt = $db->prepare($q);
        foreach ($this->getEntityKeys($classname) as $k) {
            $stmt->bindValue(':' . $k, $data[$k], self::$typemap[gettype($data[$k])]);
        }

        if (!$stmt->execute()) {
            $err = $stmt->errorInfo();
            throw new \Exception($err[2], $err[1]);
        }
        return true;
    }

//:-

    public function deleteEntityByData($classname, $data) {

        $data = self::dtoToArray($data);
        $db = $this->db;
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();
        $q = sprintf('delete from %s %s',
                        $r->getConstant(self::MAPPING_TABLE),
                        $this->getWhereClause($classname, $data, false, false, true)
        );
        error_log($q);
        $entity = $r->newInstance($data);

        $stmt = $db->prepare($q);

        foreach ($data as $k => $v) {

            if ($r->getProperty($k)) {
                $stmt->bindValue(':' . $k, $v, self::$typemap[gettype($v)]);
            }
        }

        try {
            if (!$stmt->execute()) {
                $err = $stmt->errorInfo();
                throw new \Exception($err[2], $err[1]);
            }
            return true;
        } catch (\Exception $e) {
            throw $e;
        }
    }

//:-

    public function findEntityById($classname, $data) {

        $data = self::dtoToArray($data);
        $db = $this->db;
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();
        $q = sprintf('select %s.* from %s %s',
                        $r->getConstant(self::MAPPING_TABLE),
                        $r->getConstant(self::MAPPING_TABLE),
                        $this->getWhereClause($classname, $data, true, false));
        $stmt = $db->prepare($q);

        $arrayKeys = $this->getEntityKeys($classname, true);
        foreach ($arrayKeys as $k) {
            $stmt->bindValue(':' . $k, $data[$k], self::$typemap[gettype($data[$k])]);
        }

        if (!$stmt->execute()) {
            error_log(sprintf("[%s] %s - %s", $_SERVER['REQUEST_URI'], date('Y-m-d h:i:s'), "QUERY: $q"), 3, "/var/www/html/logs/noverca.com/www/2.0/" . date("Ymd") . ".log");
            $err = $stmt->errorInfo();
            throw new \Exception($err[2], $err[1]);
        }
        $obj = $stmt->fetchObject($classname);
        if ($obj) {
            return $obj;
        } else {
            return null;
        }
    }

//:-

    public function findEntityByData($classname, $data) {
        $data = self::dtoToArray($data);
        $db = $this->db;
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();
        $q = sprintf('select * from %s %s',
                        $r->getConstant(self::MAPPING_TABLE),
                        $this->getWhereClause($classname, $data, false, false, true)
        );

        $entity = $r->newInstance($data);

        $stmt = $db->prepare($q);

        foreach ($data as $k => $v) {
            if ($r->getProperty($k)) {
                $stmt->bindValue(':' . $k, $v, self::$typemap[gettype($v)]);
            }
        }

        if (!$stmt->execute()) {
            $err = $stmt->errorInfo();
            throw new \Exception($err[2], $err[1]);
        }

        $rs = array();
        while ($row = $stmt->fetchObject($classname)) {
            array_push($rs, $row);
        }
        return $rs;
    }

//:-

    public function updateEntity($classname, $data, $dbConn=null) {
        $data = self::dtoToArray($data);
        $db = $dbConn ? $dbConn : PDO\Db::getInstance();
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();
        $q = sprintf('update %s set %s %s',
                        $r->getConstant(self::MAPPING_TABLE),
                        $this->getSqlUpdatePreparedValues($classname, $data),
                        $this->getWhereClause($classname, $data, false, false)
        );

        $stmt = $db->prepare($q);
        $bound = array();
        foreach ($c as $k => $v) {
            if (preg_match("/^" . self::field_PREFIX . "/", $k)) {
                if (isset($data[$v])) {
                    array_push($bound, $v);
                    $stmt->bindValue(':' . $v, $data[$v], self::$typemap[gettype($data[$v])]);
                }
            }
        }
        $stmt->execute();
        return true;
    }

//:-

    /**
     *
     * $sequence_assoc = array(
     * 		'field_name' => nome campo a cui destinare il valore ritornato dalla sequence,
     * 		'sequence_name' => nome sequence da cui estrarre il valore
     * );
     *
     *
     * @param string $classname
     * @param array $data
     * @param array $sequence_assoc
     * @return mixed
     */
    public function insertEntity($classname, $data, $sequence_assoc=array()) {
        $data = self::dtoToArray($data);
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();

        $db = $this->db;

        print_r($db->getAttribute(PDO::ATTR_DRIVER_NAME));

        print_r($data);

        $strdata = implode(',', $data);

        print_r($strdata);

        switch ($db->getAttribute(PDO::ATTR_DRIVER_NAME)) {
            case 'oci':

                if (count($sequence_assoc) > 0) {
                    /**
                     * Associazione campo - sequence passata dall'esterno
                     */
                    $SEQ_TABLE = isset($sequence_assoc[('sequence_name')]) ? $sequence_assoc['sequence_name'] : $c['SEQ_TABLE'];
                    $FLD_NAME = isset($sequence_assoc[('field_name')]) ? $sequence_assoc[('field_name')] : false;
                    if (!$FLD_NAME) {
                        throw new \Exception(_("Bisogna specificare il campo da associare alla sequence"));
                    }

                    $qid = sprintf("SELECT %s.nextVal FROM DUAL", $SEQ_TABLE);
                    $rs = $db->query($qid);
                    $nextId = $rs[0]['NEXTVAL'];

                    if ($nextId) {
                        $data[$FLD_NAME] = $nextId;
                    } else {
                        throw new \Exception(_("La sequence {$SEQ_TABLE} non ha ritornato un valore valido"));
                    }
                } else {
                    /**
                     * tentativo di associazione automatica come da specifica
                     *
                     */
                    if ($sequence_name = $c['SEQ_TABLE']) {
                        $qid = sprintf("SELECT %s.nextVal FROM DUAL", $sequence_name);
                        $rs = $db->query($qid);
                        $nextId = $rs[0]['NEXTVAL'];
                        if ($nextId) {
                            $key = $this->getEntityKeys($classname, true);
                            $data[array_shift($key)] = $nextId;
                        } else {
                            throw new \Exception(_("La sequence {$SEQ_TABLE} non ha ritornato un valore valido"));
                        }
                    }
                }
                break;
            default:
            case 'mysql':
                $nextId = $db->lastInsertId();
                break;
        }

        xdebug_var_dump(Rebel_DB_ORM_Util::getSqlfieldList($classname, $data));
//        die();

        $q = sprintf('insert into %s (%s) values (%s)',
                        $r->getConstant(self::MAPPING_TABLE),
                        Rebel_DB_ORM_Util::getSqlfieldList($classname, $data),
                        Rebel_DB_ORM_Util::getSqlInsertPreparedValues($classname, $data)
        );

        $stmt = $db->prepare($q);
        foreach ($c as $k => $v) {
            if (preg_match("/^" . self::field_PREFIX . "/", $k)) {
                if (isset($data[$v])) {
                    $stmt->bindValue(':' . $v, $data[$v], self::$typemap[gettype($data[$v])]);
                }
            }
        }
        try {
            xdebug_var_dump($q);
            $stmt->execute();
            return $nextId;
        } catch (\Exception $e) {
            throw $e;
        }
    }

//:-

    public function insertEntityWithoutSEQ($classname, $data) {
        $data = self::dtoToArray($data);
        $db = $this->db;
        $r = new ReflectionClass($classname);
        $c = $r->getConstants();

        $strdata = implode(',', $data);

        $key = $this->getEntityKeys($classname);

        $q = sprintf('insert into %s (%s) values (%s)',
                        $r->getConstant(self::MAPPING_TABLE),
                        Rebel_DB_ORM_Util::getSqlfieldList($classname, $data),
                        Rebel_DB_ORM_Util::getSqlInsertPreparedValues($classname, $data)
        );

        $stmt = $db->prepare($q);
        foreach ($c as $k => $v) {
            if (preg_match("/^" . self::field_PREFIX . "/", $k)) {
                if (isset($data[$v])) {
                    $stmt->bindValue(':' . $v, $data[$v], self::$typemap[gettype($data[$v])]);
                }
            }
        }
//		$db->beginTransaction();
        try {

            $stmt->execute();

            return true;
//			$db->commit();
        } catch (\Exception $e) {
//			$db->rollBack();
            throw new JsonException($e);
            //throw $e;
        }
    }

//:-

    public function dtoToArray($data) {
        $_data = array();
        if (is_object($data)) {

            $ro = new ReflectionObject($data);

            $pp = $ro->getProperties(ReflectionProperty::IS_PUBLIC);

            foreach ($pp as $property) {
                if ($data->{$property->name}) {
                    $_data[$property->name] = $data->{$property->name};
                }
            }
            return $_data;
        } else {

            return self::hashToLower($data);
        }
    }

//:-

    public static function hashToUpper($data) {
        $ret = array();
        foreach ($data as $k => $v) {
            $ret[($k)] = $v;
        }
        return $ret;
    }

    public static function hashToLower($data) {
        $ret = array();
        foreach ($data as $k => $v) {
            $ret[($k)] = $v;
        }
        return $ret;
    }

//
}

///:-

class ORMException extends \Exception {
    const MSG_PRIMARY_KEY_REQUIRED = 'Incomplete or not found primary key in data set';

    public function __construct($message) {

        parent::__construct($message);
    }

//:-
}

///:-
?>